<?php

namespace app\http\controller;

use PDO;
use PDOException;
use mon\util\Sql;
use mon\util\File;
use app\libs\View;
use Laf\Controller;
use app\model\AdminModel;
use app\model\SettingModel;

/**
 * 安装驱动程序
 */
class InstallController extends Controller
{
    /**
     * 配置信息
     *
     * @var array
     */
    protected $config = [
        // 安装的应用名称
        'app'       => 'CSXD客服系统',
        // 公司名称
        'company'   => 'MonLam',

        // 主页
        'home'      => '/',
        // 管理端
        'admin'     => '/admin',
        // 要求的最低PHP版本
        'php_version'   => '5.6.0',
        // 要求读写权限的目录，针对root路径
        'dir'       => [
            'public/upload',
            'storage',
            'config'
        ],
        // 开启分步写入sql
        'sql_step'  => false,
        // 导入的SQL文件
        'sql'       => STORAGE_PATH . '/csxd.sql',
        // 写入数据库配置文件的路径
        'database'  => ROOT_PATH . '/config/database.php',
        // 应用配置文件路径
        'chat_app'  => ROOT_PATH . '/config/chat.php',
        // 安装锁定文件
        'lock'      => STORAGE_PATH . '/install.lock'
    ];

    /**
     * 构造方法
     */
    public function __construct()
    {
        if (file_exists($this->config['lock'])) {
            die('你已经安装过该系统，如果想重新安装，请先删除 install.lock 文件，然后再安装。');
        }

        parent::__construct();
    }

    /**
     * 运行程序
     *
     * @return void
     */
    public function run()
    {
        $step = $this->request->get('step', 0);
        $data = [];
        $err = true;
        $template = new View();
        switch ($step) {
            case 2:
                $view = 'step2';
                // 运行环境
                $server = [
                    [
                        'title' => '操作系统',
                        'need'  => '不限制',
                        'value' => PHP_OS,
                        'status' => true,
                    ],
                    [
                        'title' => '服务器环境',
                        'need'  => '不限制',
                        'value' => $_SERVER['SERVER_SOFTWARE'],
                        'status' => true,
                    ],

                ];
                // PHP版本
                $version = PHP_VERSION >= $this->config['php_version'];
                $server[] = [
                    'title' => 'PHP版本',
                    'need'  => $this->config['php_version'],
                    'value' => PHP_VERSION,
                    'status' => $version,
                ];
                // session
                $session = function_exists('session_start');
                $server[] = [
                    'title' => 'SESSION支持',
                    'need'  => '必须',
                    'value' => $session ? '支持' : '不支持',
                    'status' => $session,
                ];
                // 文件上传
                $upload = boolval(ini_get('file_uploads'));
                $server[] = [
                    'title' => '文件上传',
                    'need'  => '必须',
                    'value' => ini_get('upload_max_filesize'),
                    'status' => $upload,
                ];
                // PDO扩展
                $pdo = extension_loaded('pdo') && extension_loaded('pdo_mysql');
                $server[] = [
                    'title' => 'PDO扩展',
                    'need'  => '必须',
                    'value' => $pdo ? '已安装' : '请安装PDO扩展',
                    'status' => $pdo,
                ];
                // GD扩展
                $gd = extension_loaded('gd');
                $server[] = [
                    'title' => 'GD扩展',
                    'need'  => '必须',
                    'value' => $gd ? '已安装' : '请安装GD扩展',
                    'status' => $gd,
                ];
                // fileinfo
                $fileinfo = extension_loaded('fileinfo');
                $server[] = [
                    'title' => 'Fileinfo扩展',
                    'need'  => '必须',
                    'value' => $fileinfo ? '已安装' : '请安装fileinfo扩展',
                    'status' => $fileinfo,
                ];
                $data['server'] = $server;
                // 文件目录读写
                $dir = [];
                $dirError = true;
                foreach ($this->config['dir'] as $path) {
                    $item = [
                        'title' => $path,
                        'need'  => '可读可写',
                        'value' => '',
                        'status' => false,
                    ];
                    // 先判断是否可以写入
                    $write = $this->testWriteDir($path);
                    if (!$write) {
                        $item['value'] = '不可写';
                        $item['status'] = false;
                        $dirError && $dirError = false;
                    } else {
                        // 判断是否可以读取
                        $read = $this->testReadDir($path);
                        if (!$read) {
                            $item['value'] = '不可读';
                            $item['status'] = false;
                            $dirError && $dirError = false;
                        } else {
                            $item['value'] = '可读可写';
                            $item['status'] = true;
                        }
                    }
                    $dir[] = $item;
                }
                $data['dir'] = $dir;

                // 验证是否存在错误
                if ($version && $pdo && $gd && $upload && $gd && $fileinfo && $dirError) {
                    $err = false;
                }
                break;
            case 3:
                $view = 'step3';
                break;
            case 4:
                if ($this->request->isPost() && $this->request->get('install')) {
                    $code = $this->request->get('code', '99999');
                    if ($code == '99999') {
                        return $this->success('安装已完成', ['code' => 99999]);
                    }
                    // TODO 按需添加更多安装例流程
                    switch ($code) {
                        case '0':
                            // 写入数据库配置文件
                            $databaseConfig = [
                                // 服务器地址
                                'host'      => $this->request->post('host'),
                                // 数据库名
                                'database'  => $this->request->post('database'),
                                // 用户名
                                'username'  => $this->request->post('username'),
                                // 密码
                                'password'  => $this->request->post('password'),
                                // 端口
                                'port'      => $this->request->post('port'),
                            ];
                            $configView = new View(APP_PATH . '/view/install/config/', 'template');
                            $content = $configView->fetch('database', $databaseConfig);
                            $content = '<?php ' . PHP_EOL . $content;

                            $save = File::instance()->createFile($content, $this->config['database'], false);
                            if (!$save) {
                                return $this->error('创建数据库配置文件失败');
                            }
                            return $this->success('创建数据库配置文件成功', ['code' => 10]);
                        case '10':
                            // 创建数据库
                            $databaseName = $this->request->post('database');
                            if (empty($databaseName)) {
                                return $this->error('创建数据库异常');
                            }
                            $dsn = "mysql:host={$this->request->post('host')};port={$this->request->post('port')};charset=utf8";
                            try {
                                $sql = "CREATE DATABASE IF NOT EXISTS `{$databaseName}` DEFAULT CHARACTER SET utf8;";
                                $pdo = new PDO($dsn, $this->request->post('username'), $this->request->post('password'));
                                $create = $pdo->exec($sql);

                                return $this->success("创建数据库[{$databaseName}]成功", ['code' => 20]);
                            } catch (PDOException $e) {
                                return $this->error('创建数据库失败，ERR => ' . $e->getMessage());
                            }
                        case '20':
                            // 读取sql文件
                            $sqls = Sql::instance()->parseFile($this->config['sql']);
                            $total = count($sqls);
                            $line = $this->request->get('line', 0);
                            if ($line >= ($total - 1)) {
                                return $this->success('导入数据完成', ['code' => 30]);
                            }

                            $dsn = "mysql:host={$this->request->post('host')};port={$this->request->post('port')};dbname={$this->request->post('database')};charset=utf8";
                            try {
                                $pdo = new PDO($dsn, $this->request->post('username'), $this->request->post('password'));
                                // 是否启用分步执行sql
                                if ($this->config['sql_step']) {
                                    // 当前执行的sql
                                    $sql = isset($sqls[$line]) ? $sqls[$line] : '';
                                    if (empty($sql)) {
                                        return $this->error("导入数据异常[{$line}]");
                                    }
                                    // 获取创建表的信息
                                    preg_match('/CREATE TABLE (IF NOT EXISTS )?`([^ ]*)`/is', $sql, $matches);
                                    if (empty($matches)) {
                                        $msg = "导入数据[{$line}]成功";
                                    } else {
                                        $msg = "创建数据表[{$matches[2]}]成功";
                                    }
                                    $save = $pdo->exec($sql);
                                    return $this->success($msg, ['code' => 20, 'line' => $line + 1]);
                                } else {
                                    // 一次性写入sql
                                    foreach ($sqls as $sql) {
                                        $save = $pdo->exec($sql);
                                    }
                                    return $this->success('导入数据成功', ['code' => 30]);
                                }
                            } catch (PDOException $e) {
                                return $this->error('导入数据失败，ERR => ' . $e->getMessage());
                            }
                        case '30':
                            // 创建管理员账户
                            $username = $this->request->post('admin');
                            $password = $this->request->post('admin_passwod');
                            if (!$username  || !$password) {
                                return $this->error('管理员账号信息异常');
                            }
                            // 重新生成密码
                            $salt = randString(6, 0);
                            $password = AdminModel::instance()->encodePassword($password, $salt);
                            $save = AdminModel::instance()->where('id', 1)->update([
                                'username'  => $username,
                                'password'  => $password,
                                'salt'      => $salt,
                                'update_time' => $_SERVER['REQUEST_TIME'],
                                'create_time' => $_SERVER['REQUEST_TIME'],
                            ]);
                            if (!$save) {
                                return $this->error('创建管理员账户失败');
                            }

                            return $this->success('创建管理员账户成功', ['code' => 31]);
                        case '31':
                            // 写入客服配置
                            $chatConfig = [
                                // websocket地址
                                'server_address'           => 'WebSocket链接地址，使用ssl则需使用wss',
                                // 访客称呼
                                'chat_username'          => '默认的游客称呼',
                                // 最大文件上传尺寸
                                'chat_upload_maxsize'    => '客户端文件上传尺寸最大限制，单位：K',
                                // 允许上传文件类型
                                'chat_upload_type'       => '客户端文件上传类型限制，“,”分割'
                            ];
                            $inserData = [];
                            foreach ($chatConfig as $index => $remark) {
                                $inserData[] = [
                                    'group' => 'chat',
                                    'index' => $index,
                                    'value' => $this->request->post($index, ''),
                                    'remark' => $remark,
                                    'create_time' => time(),
                                    'update_time' => time()
                                ];
                            }

                            // 处理标题组
                            $titleSetting = SettingModel::instance()->getInfo(['group' => 'group_title', 'index' => 'chat']);
                            if ($titleSetting) {
                                // 存在则更新
                                $save = SettingModel::instance()->save(['value' => '客服应用', 'remark' => '客服应用相关配置信息'], ['id' => $titleSetting['id']]);
                            } else {
                                // 不存在则写入
                                $save = SettingModel::instance()->save([
                                    'group' => 'group_title',
                                    'index' => 'chat',
                                    'value' => '客服应用',
                                    'remark' => '客服应用相关配置信息'
                                ]);
                            }
                            if (!$save) {
                                return $this->error('写入客服应用配置失败[1]');
                            }

                            // 写入配置
                            $delOldSetting = SettingModel::instance()->where('group', 'chat')->delete();
                            if ($delOldSetting === false) {
                                return $this->error('写入客服应用配置失败[2]');
                            }
                            $insertSetting = SettingModel::instance()->insertAll($inserData);
                            if (!$insertSetting) {
                                return $this->error('写入客服应用配置失败[3]');
                            }

                            return $this->success('导入客服配置成功', ['code' => 99999]);
                        case '99999':
                            return $this->success('安装已完成', ['code' => 99999]);
                        default:
                            return $this->error('未知安装错误');
                    }
                }
                $view = 'step4';
                $query = $this->request->post();
                $template->assign('query', $query);
                break;
            case '5':
                $view = 'step5';
                File::instance()->createFile(time(), $this->config['lock'], false);
                break;
            default:
                $view = 'step1';
                break;
        }

        $template->assign($data);
        $template->assign('err', $err);
        $template->assign('config', $this->config);
        return $template->fetch('install/' . $view);
    }

    /**
     * 测试写入
     *
     * @param string $dir
     * @return boolean
     */
    public function testWriteDir($dir)
    {
        $dir = ROOT_PATH . '/' . $dir;
        if (is_file($dir)) {
            return is_writable($dir);
        } else {
            $tfile = "_test.txt";
            $fp = @fopen($dir . "/" . $tfile, "w");
            if (!$fp) {
                return false;
            }
            fclose($fp);
            $rs = @unlink($dir . "/" . $tfile);
            if ($rs) {
                return true;
            }
            return false;
        }
    }

    /**
     * 测试读取
     *
     * @param [type] $dir
     * @return void
     */
    public function testReadDir($dir)
    {
        $dir = ROOT_PATH . '/' . $dir;
        return is_readable($dir);
    }
}
